package com.tang.script.command;

import com.tang.script.ScriptCommand;
import com.tang.script.ScriptEngine;
import com.tang.script.SystemVariableContainer;
import com.tang.script.exceptions.ScriptException;
import com.tang.script.extrator.ConstantsExtractor;
import com.tang.script.extrator.FunctionsExtractor;
import com.tang.script.extrator.VariablesExtractor;
import com.tang.script.functions.FunctionsLinker;
import com.tang.script.variable.Constant;
import com.tang.script.variable.Variable;
import com.tang.script.variable.VariableValueGetter;
import com.tang.script.variable.VariablesContainer;
import lombok.Getter;
import lombok.Setter;

import javax.script.Bindings;
import javax.script.CompiledScript;
import javax.script.SimpleBindings;
import java.util.*;

@Getter
@Setter
public abstract class AbstractScriptCommand<T> implements ScriptCommand<T> {
    protected ScriptEngine scriptEngine;
    protected String scriptText;
    protected String resolvedScriptText;
    protected Map<String, Variable> variables = new HashMap<>();
    protected Map<String, Constant> constants = new HashMap<>();
    protected List<String> functions = new LinkedList<>();

    protected Bindings binding;
    VariableValueGetter variableValueGetter = new VariableValueGetter();
    VariablesContainer variablesContainer;
    protected CompiledScript script;

    public AbstractScriptCommand(String scriptText, VariablesContainer variablesContainer) {
        this.scriptText = scriptText;
        this.variablesContainer = variablesContainer;
    }

    @Override
    public T execute() {
        initCommand();

        ConstantsExtractor constantsExtractor = ConstantsExtractor.get();
        constantsExtractor.extracte(this);

        VariablesExtractor variablesExtractor = VariablesExtractor.get();
        variablesExtractor.extracte(this);

        FunctionsExtractor functionsExtractor = FunctionsExtractor.get();
        functionsExtractor.extracte(this);

        FunctionsLinker functionsLinker = FunctionsLinker.get();
        functionsLinker.linkFunctions(this);

        defineVariables();

        return doExecute();
    }

    protected void defineVariables() {
        variableValueGetter.setVariablesContainer(getVariablesContainer());
        for (Constant constant : constants.values()) {
            Object value = getConstantValue(constant);
            binding.put(constant.getConstName(), value);
        }

        for (Variable variable : variables.values()) {
            binding.put(variable.getVarName(), variableValueGetter.getVariable(variable, variablesContainer));
        }

        resolveContext();
    }

    private void resolveContext() {
        CommandContext context = new CommandContext();
        context.setAbstractScriptCommand(this);
        CommandContext.setContextThreadLocal(context);
    }

    private Object getConstantValue(Constant constant) {
        SystemVariableContainer systemVariableContainer = getScriptEngine().getSystemVariableContainer();
        if (Objects.nonNull(systemVariableContainer) && systemVariableContainer.hasSystemVariable(constant)) {
            return systemVariableContainer.getValue(constant);
        }
        return variableValueGetter.getVariable(constant, variablesContainer);
    }

    protected void initCommand() {
//        scriptText = scriptText.replaceAll(" ", "");
        setResolvedScriptText(getScriptText());
        binding = new SimpleBindings();
    }

    public T doExecute() {
        script = getGroovyShell();
        try {
            Object result = script.eval(binding);
            return toResult(result);
        } catch (javax.script.ScriptException e) {
            throw new RuntimeException(e);
        }
    }

    public T toResult(Object result) {
        if (Objects.isNull(result)) return null;
        if (getResultClass().isAssignableFrom(result.getClass())) {
            return (T) result;
        }
        if (getResultClass() == String.class) {
            return (T) result.toString();
        }
        if (getResultClass() == Boolean.class && result instanceof Number) {
            Number number = (Number) result;
            return (T) Boolean.valueOf(!number.equals(0));
        }
        throw new ScriptException("输出类型无法强制转换, 从" + result.getClass() + "到" + getResultClass());
    }

    public abstract Class getResultClass();

    public CompiledScript getGroovyShell() {
        return scriptEngine.getScript(this.scriptText, this.resolvedScriptText);
    }

    public CompiledScript getGroovyShell(String script) {
        return scriptEngine.getScript(script, script);
    }
}
